package com.chaosj.webmagic.md;

/**
 * @className: MdConverterPipeline
 * @description:
 * @author: caoyangjie
 * @date: 2022/1/4
 **/

import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.text.StrFormatter;
import com.chaosj.webmagic.md.entity.BlogArticle;
import com.chaosj.webmagic.md.entity.ScrapeArticle;
import com.google.common.base.Splitter;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;
import org.jsoup.parser.Tag;
import org.jsoup.select.Elements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import us.codecraft.webmagic.ResultItems;
import us.codecraft.webmagic.Task;
import us.codecraft.webmagic.pipeline.CollectorPipeline;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * Store results in files.<br>
 *
 * @author code4crafter@gmail.com <br>
 * @since 0.1.0
 */
public class MdConverterPipeline implements CollectorPipeline {

    private Logger logger = LoggerFactory.getLogger(getClass());
    private List<ResultItems> collector = new ArrayList<ResultItems>();

    @Override
    public List<ResultItems> getCollected() {
        return collector;
    }
    /**
     * create a FilePipeline with default path"/data/webmagic/"
     */
    public MdConverterPipeline() {
    }

    @Override
    public void process(ResultItems resultItems, Task task) {
        List<ResultItems> nextPages = resultItems.get("nextPages");
        if(!CollectionUtils.isEmpty(nextPages)){
            collector.addAll(nextPages);
        }
        String topic = Optional.ofNullable(resultItems.getRequest().getExtra("topic")).orElse("").toString();
        BlogArticle blog = resultItems.get("blog");
        if( blog==null ){
            return;
        }
        String url = blog.getUrl();
        String author = blog.getAuthor();
        String title = StorageFacade.analyzeTitle(blog.getTitle());
        String content = blog.getContent();
        content = getTextContent(Jsoup.parse(content), StrFormatter.format("{}/{}", StorageFacade.storagePath(topic), title));
        content += "\n \n "+author+": [原文地址]("+url+") ";
        String finalContent = content;
        String finalTitle = title;
        StorageFacade.loadOrDefault().stream().forEach(ism->{
            logger.info(StrFormatter.format("存储路径: {}/{}/README.md", StorageFacade.storagePath(topic), finalTitle));
            ism.save(new ScrapeArticle()
                    .setTopic(topic)
                    .setScrapeDate(LocalDate.now())
                    .setOriginalUrl(url.toString())
                    .setTitle(title)
                    .setTags(StorageFacade.analyze(title))
                    .setContent(finalContent)
                    .setLocalPath(StrFormatter.format("{}/{}/README.md", StorageFacade.storagePath(topic), finalTitle))
            );
        });
    }

    private static int indentation = -1;
    private static boolean orderedList = false;
    public static boolean selfLink = true;
    public static String PREFIX_URL_PATH = "./";
    private static Snowflake snowflake = new Snowflake(1,1);

    public static String getTextContent(Element element, String imageStoragePath) {
        return getTextContent(element, false, imageStoragePath);
    }

    private static String getTextContent(Element element, boolean codeBlock, String imageStoragePath) {
        ArrayList<MDLine> lines = new ArrayList<MDLine>();
        List<Node> children = element.childNodes();
        for (Node child : children) {
            if (child instanceof TextNode) {
                TextNode textNode = (TextNode) child;
                MDLine line = getLastLine(lines);
                if (line.getContent().equals("")) {
                    if (!textNode.isBlank()) {
                        if (codeBlock) {
                            line.append(textNode.getWholeText().replaceAll("#", "/#").replaceAll("\\*", "/\\*"));
                        } else {
                            line.append(textNode.text().replaceAll("#", "/#").replaceAll("\\*", "/\\*"));
                        }
                    }
                } else {
                    line.append(textNode.text().replaceAll("#", "/#").replaceAll("\\*", "/\\*"));
                }

            } else if (child instanceof Element) {
                Element childElement = (Element) child;
                processElement(childElement, lines, imageStoragePath);
            } else {
                System.out.println();
            }
        }

        int blankLines = 0;
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < lines.size(); i++) {
            String line = lines.get(i).toString().trim();
            if (line.equals("")) {
                blankLines++;
            } else {
                blankLines = 0;
            }
            if (blankLines < 2) {
                result.append(line);
                if (i < lines.size() - 1) {
                    result.append("\n");
                }
            }
        }

        return result.toString();
    }

    private static void processElement(Element element, ArrayList<MDLine> lines, String imageStoragePath) {
        Tag tag = element.tag();

        String tagName = tag.getName();
        if (tagName.equals("div")) {
            div(element, lines, imageStoragePath);
        } else if (tagName.equals("p")) {
            p(element, lines, imageStoragePath);
        } else if (tagName.equals("br")) {
            br(lines);
        } else if (tagName.matches("^h[0-9]+$")) {
            h(element, lines, imageStoragePath);
        } else if (tagName.equals("strong") || tagName.equals("b")) {
            strong(element, lines, imageStoragePath);
        } else if (tagName.equals("em")) {
            em(element, lines, imageStoragePath);
        } else if (tagName.equals("hr")) {
            hr(lines);
        } else if (tagName.equals("a")) {
            a(element, lines, imageStoragePath);
        } else if (tagName.equals("img")) {
            img(element, lines, imageStoragePath);
        } else if (tagName.equals("code")) {
            code(element, lines, imageStoragePath);
        } else if (tagName.equals("ul")) {
            ul(element, lines, imageStoragePath);
        } else if (tagName.equals("ol")) {
            ol(element, lines, imageStoragePath);
        } else if (tagName.equals("li")) {
            li(element, lines, imageStoragePath);
        } else if (tagName.equals("pre")) {
            // 个人添加
            pre(element, lines, imageStoragePath);
        } else {
            MDLine line = getLastLine(lines);
            line.append(getTextContent(element, imageStoragePath));
        }
    }


    private static MDLine getLastLine(ArrayList<MDLine> lines) {
        MDLine line;
        if (lines.size() > 0) {
            line = lines.get(lines.size() - 1);
        } else {
            line = new MDLine(MDLine.MDLineType.None, 0, "");
            lines.add(line);
        }

        return line;
    }

    private static void div(Element element, ArrayList<MDLine> lines, String imageStoragePath) {
        MDLine line = getLastLine(lines);
        String content = getTextContent(element, imageStoragePath);
        if (!content.equals("")) {
            if (!line.getContent().trim().equals("")) {
                lines.add(new MDLine(MDLine.MDLineType.None, 0, ""));
                lines.add(new MDLine(MDLine.MDLineType.None, 0, content));
                lines.add(new MDLine(MDLine.MDLineType.None, 0, ""));
            } else {
                if (!content.trim().equals("")) {
                    line.append(content);
                }
            }
        }
    }

    private static void p(Element element, ArrayList<MDLine> lines, String imageStoragePath) {
        MDLine line = getLastLine(lines);
        if (!line.getContent().trim().equals("")) {
            lines.add(new MDLine(MDLine.MDLineType.None, 0, ""));
        }
        lines.add(new MDLine(MDLine.MDLineType.None, 0, ""));
        lines.add(new MDLine(MDLine.MDLineType.None, 0, getTextContent(element, imageStoragePath).replaceAll("&nbsp;","").replaceAll("NBSP;","")));
        lines.add(new MDLine(MDLine.MDLineType.None, 0, ""));
        if (!line.getContent().trim().equals("")) {
            lines.add(new MDLine(MDLine.MDLineType.None, 0, ""));
        }
    }

    private static void br(ArrayList<MDLine> lines) {
        MDLine line = getLastLine(lines);
        if (!line.getContent().trim().equals("")) {
            lines.add(new MDLine(MDLine.MDLineType.None, 0, ""));
        }
    }

    private static void h(Element element, ArrayList<MDLine> lines, String imageStoragePath) {
        MDLine line = getLastLine(lines);
        if (!line.getContent().trim().equals("")) {
            lines.add(new MDLine(MDLine.MDLineType.None, 0, ""));
        }

        int level = Integer.valueOf(element.tagName().substring(1));
        switch (level) {
            case 1:
                lines.add(new MDLine(MDLine.MDLineType.Head1, 0, getTextContent(element, imageStoragePath)));
                break;
            case 2:
                lines.add(new MDLine(MDLine.MDLineType.Head2, 0, getTextContent(element, imageStoragePath)));
                break;
            default:
                lines.add(new MDLine(MDLine.MDLineType.Head3, 0, getTextContent(element, imageStoragePath)));
                break;
        }

        lines.add(new MDLine(MDLine.MDLineType.None, 0, ""));
        lines.add(new MDLine(MDLine.MDLineType.None, 0, ""));
    }

    private static void strong(Element element, ArrayList<MDLine> lines, String imageStoragePath) {
        MDLine line = getLastLine(lines);
        line.append("**");
        line.append(getTextContent(element, imageStoragePath));
        line.append("** ");
    }

    private static void em(Element element, ArrayList<MDLine> lines, String imageStoragePath) {
        MDLine line = getLastLine(lines);
        line.append("*");
        line.append(getTextContent(element, imageStoragePath));
        line.append("*");
    }

    private static void hr(ArrayList<MDLine> lines) {
        lines.add(new MDLine(MDLine.MDLineType.None, 0, ""));
        lines.add(new MDLine(MDLine.MDLineType.HR, 0, ""));
        lines.add(new MDLine(MDLine.MDLineType.None, 0, ""));
    }

    private static void a(Element element, ArrayList<MDLine> lines, String imageStoragePath) {
        MDLine line = getLastLine(lines);
        line.append("[");
        line.append(getTextContent(element, imageStoragePath));
        line.append("]");
        line.append("(");
        String url = element.attr("href");
        line.append(url);
        String title = element.attr("title");
        if (!title.equals("")) {
            line.append(" \"");
            line.append(title);
            line.append("\"");
        }
        line.append(")");
    }

    private static void img(Element element, ArrayList<MDLine> lines, String imageStoragePath) {
        MDLine line = getLastLine(lines);

        line.append("![");
        String alt = element.attr("alt");
        line.append(alt);
        line.append("]");
        line.append("(");
        String url = element.attr("data-src");
        if( StringUtils.isEmpty(url) ){
            url = element.attr("data-original");
        }
        url = StringUtils.isBlank(url) ? element.attr("src"): url;
        if( StringUtils.isBlank(url) ){
            line.append(")");
            return;
        }else{
            if( !url.startsWith("http") ){
                url = "https:"+url;
            }
        }
        String imagePath = imagePath(url);
        line.append(imagePath);
//        line.append(selfLink? (PREFIX_URL_PATH +Splitter.on("/").splitToList(url).stream().skip(3).collect(Collectors.joining("/"))) : url);
        // 存储网络图片到本地
        StorageApi.storageImage(imageStoragePath, imagePath, url);
        String title = element.attr("title");
        if (!title.equals("")) {
            line.append(" \"");
            line.append(title);
            line.append("\"");
        }
        line.append(")");
    }

    private static String imagePath(String url){
        String urlPath = url.replace("?","/");
        List<String> imageNames = Splitter.on("/").splitToList(urlPath).stream().skip(3).collect(Collectors.toList());
        if( Arrays.asList(".bmp",".jpg",".png",".gif",".pcx",".svg",".jpeg").stream().filter(img->urlPath.endsWith(img)).findAny().isPresent() ){
            return selfLink? (PREFIX_URL_PATH +imageNames.stream().collect(Collectors.joining("/"))) : url;
        }
        List<String> newImageNames = imageNames.subList(0,imageNames.size()-1);
        String last = imageNames.get(imageNames.size()-1);
        if( last.contains("wx_fmt=") ){
            last = snowflake.nextIdStr()+"."+last.substring(last.length()-3);
        }else{
            last = snowflake.nextIdStr()+".png";
        }
        newImageNames.add(last);
        return selfLink? PREFIX_URL_PATH+newImageNames.stream().collect(Collectors.joining("/")): url;
    }

    private static void code(Element element, ArrayList<MDLine> lines, String imageStoragePath) {
        lines.add(new MDLine(MDLine.MDLineType.None, 0, ""));
        MDLine line = new MDLine(MDLine.MDLineType.None, 0, "    ");

        String codeContent = getTextContent(element, true, imageStoragePath).replace("/#", "#");
        String code = "```\n\t" + codeContent + "\n```";
        line.append(code);
        lines.add(line);
        lines.add(new MDLine(MDLine.MDLineType.None, 0, ""));
    }

    private static void ul(Element element, ArrayList<MDLine> lines, String imageStoragePath) {
        lines.add(new MDLine(MDLine.MDLineType.None, 0, ""));
        indentation++;
        orderedList = false;
        MDLine line = new MDLine(MDLine.MDLineType.None, 0, "");
        line.append(getTextContent(element, imageStoragePath));
        lines.add(line);
        indentation--;
        lines.add(new MDLine(MDLine.MDLineType.None, 0, ""));
    }

    private static void ol(Element element, ArrayList<MDLine> lines, String imageStoragePath) {
        lines.add(new MDLine(MDLine.MDLineType.None, 0, ""));
        indentation++;
        orderedList = true;
        MDLine line = new MDLine(MDLine.MDLineType.None, 0, "");
        line.append(getTextContent(element, imageStoragePath));
        lines.add(line);
        indentation--;
        lines.add(new MDLine(MDLine.MDLineType.None, 0, ""));
    }

    private static void li(Element element, ArrayList<MDLine> lines, String imageStoragePath) {
        MDLine line;
        if (orderedList) {
            line = new MDLine(MDLine.MDLineType.Ordered, indentation,
                    getTextContent(element, imageStoragePath));
        } else {
            line = new MDLine(MDLine.MDLineType.Unordered, indentation,
                    getTextContent(element, imageStoragePath));
        }
        lines.add(line);
    }

    private static void pre(Element element, ArrayList<MDLine> lines, String imageStoragePath) {
        Elements pre = element.select("pre");
        for (Element item : pre) {
            Elements code = item.children();
            for (Element child : code) {
                processElement(child, lines, imageStoragePath);
            }
            MDLine line = new MDLine(MDLine.MDLineType.None, 0, "");
            line.append("");
            lines.add(line);
            lines.add(new MDLine(MDLine.MDLineType.None, 0, "\n\t"));
        }

    }
}
